package edu.biwu.sms02;

import edu.biwu.sms.MiddleStudent;
import edu.biwu.sms.PrimaryStudent;
import edu.biwu.sms.Student;
import edu.biwu.constant.EnrollmentStatus;

import java.io.*;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;

/**
 * > 需求:
 * a.能够获取指定的学生对象的任意属性的值
 * b.能够设置指定的学生对象的任意属性的值
 */
public class StudentManagementSystem02 {
    /*定义一个集合去存储Student元素*/
    private ArrayList<Student> students = new ArrayList<>();




    /**
     * @param student      要获取的指定的学生对象
     * @param propertyName 指定学生对象的属性
     * @return 该学生对象对应的属性值
     */
    public Object getPropertyValue(Student student, String propertyName) throws NoSuchFieldException, IllegalAccessException {
        //1.获取当前对象代表的字节码
        Class<?> cls = student.getClass();
        System.out.println(cls);
        Field field=null;
        while (cls!=null) {//如果cls为null，说明已经找到Object类
            try {
                field = cls.getDeclaredField(propertyName);//先获取本字节码中是否有该属性
                break;//如果有获取到指定属性，就立刻终止循环
            } catch (NoSuchFieldException e) {
                //如果治党catch说明在当前字节码中没有找到对应的属性，此时获取父类的字节码
                cls=cls.getSuperclass();
            }
        }
        //当循环执行完毕
        //1.找到，field被赋值，通过break中断循环
        //2.没找到，从当前字节码开始-->找到父类的字节码-->....->一直找到Object类字节码都没有找到
        if (field==null){
            throw new NoSuchFieldException("整个继承体系都找不到该属性："+propertyName);
        }

        //2.跳过java的权限检查
        field.setAccessible(true);
        //3.返回指定学生对象的属性值
        return field.get(student);
    }

    /**
     * @param student      指定的学生对象
     * @param propertyName 指定的学生对象的属性
     * @param value        指定的属性值
     */
    public void setPropertyValue(Student student, String propertyName, Object value) throws NoSuchFieldException, IllegalAccessException {
        //1.获取当前对象代表的字节码
        Class<?> cls = student.getClass();
        System.out.println(cls);
        Field field=null;
        while (cls!=null) {//如果cls为null，说明已经找到Object类
            try {
                field = cls.getDeclaredField(propertyName);//先获取本字节码中是否有该属性
                break;//如果有获取到指定属性，就立刻终止循环
            } catch (NoSuchFieldException e) {
                //如果治党catch说明在当前字节码中没有找到对应的属性，此时获取父类的字节码
                cls=cls.getSuperclass();//对于Object没有父类，当找到找到Object类的时候就为空了
            }
        }
        //当循环执行完毕
        //1.找到，field被赋值，通过break中断循环
        //2.没找到，从当前字节码开始-->找到父类的字节码-->....->一直找到Object类字节码都没有找到
        if (field==null){
            throw new NoSuchFieldException("整个继承体系都找不到该属性："+propertyName);
        }

        //2.跳过java的权限检查
        field.setAccessible(true);

        //3.设置指定的学生对象的属性值
        field.set(student, value);
    }


    /**
     * @param resultPath 指定写入的目的地
     */
    public void writeRankedStudentToFile(String resultPath) throws IOException {
        //1.先按照总分数从高到低排名
        sortByTotalScore();

        //2.再将排好序的学生信息写入到resultpath指定文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter(resultPath));
        for (Student student : students) {
            bw.write(student.toString());
            bw.newLine();//每写入一个学生信息就换一行，最终实现一行一个学生信息
        }
        //3。关流释放资源
        bw.close();
    }


    /**
     * @param filepath 数据源地址，例如：小学生信息.txt的路径
     */
    public void loadStudentFromFile(String filepath) throws IOException {
        //1.先使用FileReader关联指定文件路径
        //再利用BufferedReader进行包装，为了使用readLine()方法
        //File file = new File(filepath);
        //BufferedReader br = new BufferedReader(new FileReader(file));
//<----
        InputStream is = StudentManagementSystem02.class.getClassLoader().getResourceAsStream(filepath);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
//----->
        String line;
        while ((line = br.readLine()) != null) {
            //例如第一行：20241316,小新,77,81,2013-10-12,233434@qq.com
            //data={20241316, 小新, 77, 81, 2013-10-12, 233434@qq.com }
            //小学生和中学生通用属性数据封装
            String[] data = line.split(",");
            Integer studentId = Integer.parseInt(data[0]);//解析出学号
            String name = data[1];
            Integer chineseScore = Integer.parseInt(data[2]);
            Integer mathScore = Integer.parseInt(data[3]);
            LocalDate enrollmentDate = LocalDate.parse(data[4]);
            String email = data[5];
            EnrollmentStatus enrollByDesc = EnrollmentStatus.getEnrollByDesc(data[6]);//根据中文学籍状态名称获取对应的枚举常量
            if (filepath.equals("中学生信息.txt")) {
                //封装中学生特有的信息：历史成绩和物理成绩
                //例如某个中学生: 20241316,大新,97,81,2013-10-12,233434123@qq.com,100,98
                Integer historyScore = Integer.parseInt(data[7]);
                Integer physicsScore = Integer.parseInt(data[8]);
                MiddleStudent middleStudent = new MiddleStudent(studentId, name, chineseScore, mathScore, enrollmentDate
                        , email,enrollByDesc ,historyScore, physicsScore);
                students.add(middleStudent);
            } else if (filepath.equals("小学生信息.txt")) {
                PrimaryStudent primaryStudent = new PrimaryStudent(studentId, name, chineseScore, mathScore, enrollmentDate, email,enrollByDesc);
                students.add(primaryStudent);
            }
        }
        //关流释放资源
        br.close();
    }


    /**
     * @param student       当前学生(将来头像图片的路径与该学生绑定)
     * @param localFilePath 模拟从本地选的一张图片
     * @param serverPath    模拟上传到服务器
     */
    public void uploadStudentAvatar(Student student, String localFilePath, String serverPath) throws Exception {
        //1.利用FileInputStream关联读的文件，然后再利用BufferedInputStream包装FileInputStream，目的提高读效率
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(localFilePath));

        //2.将上传的图片进行名称的唯一性处理（使用UUID），就算上传相同的图片也不会发生覆盖，每上传的图片都会生成一个随机且唯一的名称
        String filename = new File(localFilePath).getName();//获取文件的名称
        String extension = filename.substring(filename.lastIndexOf("."));//截取文件的后缀名.jpg

        String newfileName = UUID.randomUUID().toString() + extension;//随机生出一串序列加上上面的后缀名，组合成该头像的唯一性名称

        //3.利用FileOutputStream关联写的文件，然后再利用BufferedOutputStream包装FileOutputStream，目的提高写效率
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(serverPath + "/" + newfileName));

        //4.读写操作
        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }

        //5.关流释放资源
        bis.close();
        bos.flush();

        //6.将该图片绑定到当前学生
        student.setAvatar(serverPath + "/" + newfileName);

    }


    //    //专门统计存储的有效学生元素个数
//    private int count;   //集合中有size方法获取元素个数
    public Map<Integer, Integer> getScoreStatisticBySubjectSubject(String subject) {
        HashMap<Integer, Integer> scoreStatistic = new HashMap<>();
        try {
            for (Student student : students) {
                int score = getScoreBySubject(student, subject);
                if (scoreStatistic.containsKey(score)) {
                    scoreStatistic.put(score, scoreStatistic.get(score) + 1);
                } else {
                    scoreStatistic.put(score, 1);
                }
            }
        } catch (NullPointerException e) {
            System.out.println("处理学生信息出错:" + e.getMessage());
            e.printStackTrace();
        }
        return scoreStatistic;
    }


    /**
     * @param subject 指定科目，例如：“语文”
     * @return 指定学科最高成绩的学生信息
     */
    public Student findStudentWithHigestScore(String subject) {//参数为下面的方法
        try {
            return Collections.max(students, new Comparator<Student>() {
                @Override
                public int compare(Student s1, Student s2) {
                    //按照指定科目的成绩比较
                    return getScoreBySubject(s1, subject) - getScoreBySubject(s2, subject);
                }
            });
        } catch (NoSuchElementException e) {
            System.out.println("没有找到任何学生:" + e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说名出问题，直接返回null
        } catch (ClassCastException e) {
            System.out.println("学生的分数不可比较：" + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }


    /**
     * @param subject 指定科目，例如：“语文”
     * @return 指定学科最高成绩的学生信息
     */
    public Student findStudentWithLowestScore(String subject) {//参数为下面的方法
        try {
            return Collections.min(students, new Comparator<Student>() {
                @Override
                public int compare(Student s1, Student s2) {
                    //按照指定科目的成绩比较
                    return getScoreBySubject(s1, subject) - getScoreBySubject(s2, subject);
                }
            });
        } catch (NoSuchElementException e) {
            System.out.println("没有找到任何学生:" + e.getMessage());
            e.printStackTrace();
            return null;//执行catch里面说名出问题，直接返回null
        } catch (ClassCastException e) {
            System.out.println("学生的分数不可比较：" + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据每个学生的总分数进行成绩从高到底排序，即排名
     */
    public void sortByTotalScore() {

        //方式一：
//        //1.构建一个TreeSet并且利用比较器指定比较规则
//        TreeSet<Student> temps = new TreeSet<>(new Comparator<Student>() {
//            @Override
//            public int compare(Student o1, Student o2) {
//                //如果两个学生的总分数相同，可能导致另外一个学生无法存入。
//                int result = o2.getTotalScore() - o1.getTotalScore();
//                if (result == 0) {
//                    //如果分数相同，再去比较姓名
//                    //再加上姓名，也有可能存在姓名和分数都相同的情况
//                    int nameResult = o2.getName().compareTo(o1.getName());
//                    if (nameResult == 0) {
//                        return o2.getStudentid() - o1.getStudentid();
//                    } else {
//                        //总分相同，姓名不同
//                        return nameResult;
//                    }
//                } else {
//                    return result;//总分数不同，不再比较次要条件
//                }
//            }
//        });
//        temps.addAll(students);//将指定的容器中的元素拷贝到当前集合，这里相当于将ArrayList中的元素复制到temos集合中
//        students.clear();//清空系统中原有的元素
//        students.addAll(temps);//再把TreeSet中的按照总分数排好序的元素放回ArrayList中

        //方法二:  JDK8及之后

        try {
            students.sort(new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o2.getTotalScore() - o1.getTotalScore();
                }
            });
        } catch (Exception e) {
            System.out.println("排序过程中出错：" + e.getMessage());
            e.printStackTrace();
        }

    }


    /**
     * 1.添加一个根据序号更新学生信息的方法
     *
     * @param number     学生的编号（从1开始）
     * @param newStudent 学生的新的信息（学生对象中封装新的信息）
     * @return 更改前的学生对象
     */
    public Student updateStudentByIndex(int number, Student newStudent) {
        try {
            if (number >= 1 && number <= students.size()) {
                return students.set(number - 1, newStudent);
            } else {
                //System.out.println("不存在序号为"+number+"的学生信息！");
                //return null;
                throw new RuntimeException("指定的学生序号超出范围：" + number);
            }
        } catch (Exception e) {
            System.out.println("更新学生信息时出错：" + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }


    /**
     * @return 随机取出学生
     */
    public Student randomRollCall() {
        try {
            return students.get(new Random().nextInt(students.size()));
        } catch (Exception e) {
            System.out.println("随机点名出错：" + e.getMessage());
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 添加学生
     *
     * @param student
     */
    public void addStudent(Student student) {
        try {
            if (student != null && validateStudentEmail(student)) {
                students.add(student);
            }

        } catch (Exception e) {
            System.out.println("添加学生时出错：" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 邮箱的匹配校验
     *
     * @param student 学生对象
     * @return 返回邮箱匹配校验结果
     */
    public boolean validateStudentEmail(Student student) {
        //1.健壮性判断  防空处理
        if (student == null || student.getEmall() == null) {
            throw new RuntimeException("学生为null或邮箱为空null");
        } else {
            //2.邮箱匹配判断
            return student.getEmall().matches("\\w+@\\w+\\.\\w+");
        }
    }

    /**
     * 遍历指定容器中的元素学生
     *
     * @param students 指定的学生容器
     */
    public void showAllStudent(Collection<Student> students) {
        try {
            int index = 1;
            for (Student student : students) {
                System.out.println((index++) + " " + student.toString());
            }
        } catch (Exception e) {
            System.out.println("显示学生信息时出错：" + e.getMessage());
            e.printStackTrace();
        }
    }


    /**
     * 遍历容器中的学生
     */
    public void showAllStudent() {
        showAllStudent(students);
    }

    /**
     * 判断系统中是否存在该学生(要求如果系统中存在该学生的姓名，我们就认为该学生在系统中存在)
     *
     * @param stu 所要查询的学生   (其实这里在比较的时候，依旧是参考的重写的equals方法)
     * @return 返回学生信息
     */
    public boolean constains(Student stu) {
        try {
            return students.contains(stu);
        } catch (Exception e) {
            throw new RuntimeException(e);//将异常对象近一倍封装，将来可以打印e的信息
        }
    }


    /**
     * @param studentid 学生学号
     * @return 根据学生学号，查询到学生信息(封装到学生对象中)
     */
    public Student findStudentById(int studentid) {
        try {
            for (Student student : students) {
                if (student.getStudentid() == studentid) {
                    return student;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;//说明该学号的学生不存在
    }

    /**
     * @param subject 指定科目
     * @return 所有学生指定科目的平均成绩
     */
    public int getAverageScoreBySubject(String subject) {
        try {
            int totalScore = 0;//对所有学生某一门成绩累加求和
            for (Student student : students) {
                int score = getScoreBySubject(student, subject);
                totalScore += score;
            }
            return students.size() == 0 ? 0 : totalScore / students.size();
        } catch (Exception e) {
            throw new RuntimeException("计算平均分时出错：", e);
        }
    }

    /**
     * @param student 学生对象
     * @param subject 科目名称
     * @return 当前学生指定科目的成绩
     */
    private int getScoreBySubject(Student student, String subject) {
        //JDK新特性
        try {
            switch (subject) {
                case "语文" -> {
                    return student.getChineseScore();
                }
                case "数学" -> {
                    return student.getMathScore();
                }
                case "历史" -> {
                    if (student instanceof MiddleStudent middleStudent) {
                        return middleStudent.getHistoryScore();
                    }
                }
                case "物理" -> {
                    if (student instanceof MiddleStudent middleStudent) {
                        return middleStudent.getPhysicsScore();
                    }
                }
//            default -> {
//                return 0;
//            }转换为下面的哪一行
            }
            return 0;//传递的subject没有找到就返回0
        } catch (Exception e) {
            throw new RuntimeException("获取当前学生：" + subject + "时出错", e);
        }
    }

    /**
     * @param date 指定日期
     * @return 存储了满足条件的学生信息的集合
     */
    public Collection<Student> getStudentsEnrolledBefore(String date) {
        //1.定义一个数组，该数组专门用来存储满足条件的学生信息
        Collection temps = new ArrayList();
        try {
            for (Student student : students) {
                if (student.getEnrollmentDate() != null) {
                    if (student.getEnrollmentDate().isBefore(LocalDate.parse(date)) ||
                            student.getEnrollmentDate().isEqual(LocalDate.parse(date))) {
                        temps.add(student);
                    }
                }
            }
        } catch (DateTimeParseException e) {
            System.out.println("解析日期" + date + "时出错：" + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            throw new RuntimeException("获取指定入学日期之前入学的学生出错:" + e.getMessage());
        }
        return temps;
    }

    /**
     * @param date 指定日期
     * @return 存储了满足条件的学生信息的集合
     */
    public Collection<Student> getStudentsEnrolledAfter(String date) {
        //1.定义一个集合，该数组专门用来存储满足条件的学生信息
        Collection temps = new ArrayList();
        try {
            for (Student student : students) {
                if (student != null && student.getEnrollmentDate() != null) {
                    if (student.getEnrollmentDate().isAfter(LocalDate.parse(date)) ||
                            student.getEnrollmentDate().isEqual(LocalDate.parse(date))) {
                        temps.add(student);
                    }
                }
            }
        } catch (DateTimeParseException e) {
            System.out.println("解析日期" + date + "时出错：" + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            throw new RuntimeException("获取指定入学日期之后入学的学生出错:" + e.getMessage());
        }
        return temps;
    }

}